#include "xml.h"

static const char xml_header[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n";

CXmlBuffer::CXmlBuffer(const char* name)
: CByteBuffer(name)
{
}

CXmlBuffer::CXmlBuffer(const char* inbuf, ushort inlen, const char* name)
: CByteBuffer(inbuf, inlen, name)
{
}

int CXmlBuffer::ToBlock(CByteBlock* block)
{
    ushort orgsrclen = GetSize();
    const char* orgsrc = GetByte();

	const char* pcur = orgsrc;
	const char* pend = orgsrc + orgsrclen;

	int depth = 0;
    ushort labelsize = 0;
    const char* label = NULL;
	CByteBlock* curnode = block;

	while(pcur < pend)
	{
		const char* plast = pcur;

		if(!FindLabel(pcur, (ushort)(pend-pcur), label, labelsize))
			break;
	
        if(0 == labelsize) return -1;

        switch(*label)
        {
        case '/'://node end
            {
                if(depth < 1) return -1;

                //>>check name
	            const char* right = (const char*)memchr(label+1, '>', (ushort)(pend-label));

                const ushort namesize = curnode->GetBlockName().size();

                if(NULL == right)
                {
                    if(namesize >= (ushort)(pend-label))
                        return 0;
                    return -1;
                }

                if(0 != strncmp(label+1, curnode->GetBlockName().c_str(), namesize))
                    return -1;
                //<<end

                curnode->SetBlockValue(plast, (ushort)(label-plast-1)); --depth;

                if(0 == depth)
                {
                    if(curnode->GetParent() != block) return -1;

                    return label + 1 + namesize + 1 - orgsrc; //for /name>
                }

                curnode = curnode->GetParent();
            }
			break;

		case '?'://xml define node,ignore
			break;

		case '!'://comment node,ignore
			break;

		default://node start
			{
                curnode->SetBlockValue(plast, (ushort)(label-plast-1)); //-1 for <

                CByteBlock* child = AddChild<CXmlBlock>(curnode, XML_SIGN_NODE);

                if(NULL == child) return 0;

                if(!ParseLabel(child, label, labelsize))
                    return -1;

				if('/' != *(label+labelsize-1))
				{
					curnode = child; ++depth;
				}
			}
            break;
		}
	}

    return 0;
}

bool CXmlBuffer::FindLabel(const char* &begin, ushort len, const char* &label, ushort &labelsize)
{
	const char* left = (const char*)memchr(begin, '<', len);

	if(NULL == left) return false;

	label = left + 1; len -= (ushort)(label - begin);
    
    if(len < 3) return false;

    if(strncmp(label, "!--", 3) == 0)//<!-- ... -->
	{
		len -= 3; if(0 == len) return false;
        
        const char* pcur = label + 3;

		while(1)
		{
			const char* pend = (const char*)memchr(pcur, '-', len);

			if(NULL == pend) return false;
			
            ++pend; len -= (ushort)(pend - pcur);

            if(len < 2) return false;

            if('-' == *pend && '>' == *(pend+1))
			{
				labelsize = (ushort)(pend - label - 1);

				begin = pend + 2;

				return true;
			}
            
            pcur = pend;
		}
	}

	const char* right = (const char*)memchr(label, '>', len);
	
    if(NULL == right)   return false;

	labelsize = (ushort)(right - label);

	begin = right + 1;

	return true;
}

bool CXmlBuffer::ParseLabel(CByteBlock* block, const char* label, ushort labelsize)
{
	const char* pcur = label;

	while(' ' != *pcur && '/' != *pcur && '>' != *pcur) ++pcur;

    const char* plast = pcur;

	if(' ' == *pcur)//attributes
	{
		const char* pend = label + labelsize;
		
		while(pcur < pend)
		{
            //attribute name
			while(' ' == *pcur) ++pcur;

            const char* name = pcur;

			while(' ' != *pcur && '=' != *pcur && '/' != *pcur && '>' != *pcur) ++pcur;

			ushort namesize = (ushort)(pcur - name);

            //attribute value
			pcur = (const char*)memchr(pcur, '"', pend-pcur);
            
            if(NULL == pcur) return false;

			const char* value = ++pcur;
            
            pcur = (const char*)memchr(pcur, '"', pend-pcur);
            
            if(NULL == pcur) return false;

			ushort valuesize = (ushort)(pcur - value);

            CByteBlock* child = AddChild<CXmlBlock>(block, XML_SIGN_ATTR);
            
            if(NULL == child) return false;
            
            if(child->SetBlockName(name, namesize) <= 0)
                return false;

            if(child->SetBlockValue(value, valuesize) <= 0)
                return false;

			++pcur;
		}
	}

    if(block->SetBlockName(label, (ushort)(plast-label)) <= 0)
        return false;

    return true;
}

CXmlBlock::CXmlBlock(const char* blockname)
: CByteBlock(blockname)
, m_xmlheader(xml_header)
{
}

CXmlBlock::CXmlBlock(uchar sign, CByteBlock* parent)
: CByteBlock(sign, parent)
{
}

bool CXmlBlock::SetXmlHeader()
{
    if(XML_SIGN_ROOT != m_blocksign)
        return false;
    
    m_xmlheader = xml_header;

    return true;
}

int CXmlBlock::ToBuffer(CByteBuffer* buffer)
{
    if(IsEmpty()) return -1;

    ushort size;

    if(XML_SIGN_ATTR == m_blocksign)
    {
        (*buffer).Write(" ", 1);

        size = (ushort)(m_blockname.size());

        if(0 == size) return -1;

        (*buffer).Write(m_blockname.c_str(), size);

        (*buffer).Write("=\"", 2);

        size = (ushort)(m_blockvalue.size());

        if(0 == size) return -1;

        (*buffer).Write(m_blockvalue.c_str(), size);

        (*buffer).Write("\"", 1);
    }
    else if(XML_SIGN_NODE == m_blocksign)
    {
        (*buffer).Write("<", 1);

        size = (ushort)(m_blockname.size());

        if(0 == size) return -1;

        (*buffer).Write(m_blockname.c_str(), size);

        CByteBuffer attrbuffer;

        for(BlockIterator it = m_children.begin(); it != m_children.end(); ++it)
        {
            CByteBlock* child = *it;

            if(XML_SIGN_ATTR == child->GetBlockSign())
            {
                if(child->ToBuffer(&attrbuffer) < 0)
                    return -1;
            }
        }

        size = (ushort)(attrbuffer.GetSize());

        if(size > 0) (*buffer).Write(attrbuffer.GetByte(), size);

        CByteBuffer nodebuffer;

        for(BlockIterator it = m_children.begin(); it != m_children.end(); ++it)
        {
            CByteBlock* child = *it;

            if(XML_SIGN_ATTR != child->GetBlockSign())
            {
                if(child->ToBuffer(&nodebuffer) < 0)
                    return -1;
            }
        }

        size = (ushort)(m_blockvalue.size());

        ushort nodesize = (ushort)(nodebuffer.GetSize());

        if(0 == nodesize && 0 == size)
        {
            (*buffer).Write("/>\r\n", 4);
        }
        else
        {
            (*buffer).Write(">", 1);

            if(nodesize > 0)
            {
                (*buffer).Write("\r\n", 2);

                (*buffer).Write(nodebuffer.GetByte(), nodesize);
            }

            if(size > 0)
            {
                (*buffer).Write(m_blockvalue.c_str(), size);

                if(0 != nodesize) (*buffer).Write("\r\n", 2);
            }

            (*buffer).Write("</", 2);

            (*buffer).Write(m_blockname.c_str(), m_blockname.size());

            (*buffer).Write(">\r\n", 3);
        }
    }
    else if(XML_SIGN_ROOT == m_blocksign)
    {
        (*buffer).Write(m_xmlheader.c_str(), (ushort)(m_xmlheader.size()));

        CByteBuffer nodebuffer;

        for(BlockIterator it = m_children.begin(); it != m_children.end(); ++it)
        {
            CByteBlock* child = *it;

            if(XML_SIGN_ATTR != child->GetBlockSign())
            {
                if(child->ToBuffer(&nodebuffer) < 0)
                    return -1;
            }
        }

        size = (ushort)(nodebuffer.GetSize());

        if(size < 2) return -1;
        
        (*buffer).Write(nodebuffer.GetByte(), size - 2); //-2 for removing the last "\r\n"
    }
    else
    {
        return -1;
    }

    return 1;
}

bool en_xml_node(CByteBlock& block, string name, string value)
{
    CByteBlock* child = AddChild<CXmlBlock>(&block, XML_SIGN_NODE);

    if(NULL == child) return false;

    if(child->SetBlockName(name) <= 0) return false;

    if(child->SetBlockValue(value) < 0) return false;

    return true;
}

bool en_xml_attr(CByteBlock& block, string name, string value)
{
    CByteBlock* child = AddChild<CXmlBlock>(&block, XML_SIGN_ATTR);

    if(NULL == child) return false;

    if(child->SetBlockName(name) <= 0) return false;

    if(child->SetBlockValue(value) < 0) return false;

    return true;
}

bool de_xml_node(CByteBlock& root, string name, string& value, uchar index)
{
    CByteBlock* block = root.FindChild(XML_SIGN_NODE, name.c_str(), index);

    if(NULL == block) return false;

    value = block->GetBlockValue();

    return true;
}

bool de_xml_attr(CByteBlock& root, string name, string& value, uchar index)
{
    CByteBlock* block = root.FindChild(XML_SIGN_ATTR, name.c_str(), index);

    if(NULL == block) return false;

    value = block->GetBlockValue();

    return true;
}
